package com.bocai.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.apache.log4j.LogManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;
import redis.clients.jedis.exceptions.JedisConnectionException;
import redis.clients.jedis.exceptions.JedisException;

import java.util.*;

/**
 * redis缓存操作工具类，需在RedisConfig.java中初始化JedisPool
 * 
 * @author zhaoyc
 * @version 创建时间：2017年9月4日 下午3:17:37
 */
@Component
public class RedisAdapter {

    @Autowired
    private JedisPool jedisPool;

    public RedisAdapter() {}

    public RedisAdapter(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public List<String> getPipelineResponse(final List<String> keys, final String field) {
        return execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                Pipeline pipelineJedis = jedis.pipelined();
                List<Response<String>> responses = new ArrayList<>();
                for (String key : keys) {
                    responses.add(pipelineJedis.hget(key, field));
                }
                // 一定要先sync()才能执行resp.get() 否则报错：Please close pipeline or multi
                // block before calling this method.
                pipelineJedis.sync();

                List<String> results = new ArrayList<>(responses.size());
                for (Response<String> resp : responses) {
                    results.add(resp.get());
                }
                return results;
            }
        });
    }

    /**
     * 通过pipeline方式，根据多个key得到这些key的value
     * 
     * @param keys
     * @return
     */
    public List<String> getPipelineResp(final String... keys) {
        return execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                Pipeline pipelineJedis = jedis.pipelined();
                List<Response<String>> responses = new ArrayList<>();
                for (String key : keys) {
                    responses.add(pipelineJedis.get(key));
                }
                pipelineJedis.sync();

                List<String> results = new ArrayList<>(responses.size());
                for (Response<String> resp : responses) {
                    results.add(resp.get());
                }
                return results;
            }
        });
    }

    public Jedis getJedis() {
        return execute(new JedisExecutor<Jedis>() {
            @Override
            public Jedis execute(Jedis jedis) {
                return jedis;
            }
        });
    }

    public <T> T get(final String key, final TypeReference<T> type) {
        return execute(new JedisExecutor<T>() {
            @Override
            public T execute(Jedis jedis) {
                return parseObject(jedis.get(key), type);
            }
        });
    }

    public <T> T get(final String key, final Class<T> clazz) {
        return execute(new JedisExecutor<T>() {
            @Override
            public T execute(Jedis jedis) {
                return parseObject(jedis.get(key), clazz);
            }
        });
    }

    public String get(final String key) {
        return execute(new JedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.get(key);
            }
        });
    }

    public List<String> mget(final String... keys) {
        return execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.mget(keys);
            }
        });
    }

    /**
     * 批量set到cache
     * 
     * @param keysvalues
     * @return
     * @throws JedisException
     */
    public String mset(final String... keysvalues) throws JedisException {
        return execute(new JedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.mset(keysvalues);
            }
        });
    }

    public String set(final String key, final Object o) {
        return execute(new JedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.set(key, toJsonString(o));
            }
        });

    }

    public String setex(final String key, final int seconds, final Object o) {
        return execute(new JedisExecutor<String>() {
            @Override
            public String execute(Jedis jedis) {
                return jedis.setex(key, seconds, toJsonString(o));
            }
        });
    }

    public Long incr(final String key) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.incr(key);
            }
        });
    }

    public Long del(final String... keys) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.del(keys);
            }
        });
    }



    public Long hdel(final String key, final String... fields) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hdel(key, fields);
            }
        });
    }

    public Long lpush(final String key, final Object... o) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                String[] array = new String[o.length];
                for (int i = 0; i < o.length; i++) {
                    array[i] = toJsonString(o[i]);
                }
                return jedis.lpush(key, array);
            }
        });
    }


    public Long rpush(final String key, final List<?> list) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                if (list == null || list.size() == 0) {
                    return 0L;
                }

                String[] array = new String[list.size()];
                for (int i = 0; i < list.size(); i++) {
                    array[i] = toJsonString(list.get(i));
                }
                return jedis.rpush(key, array);
            }
        });
    }

    public Long rpush(final String key, final Object o) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.rpush(key, toJsonString(o));
            }
        });
    }

    public Long rpush(final String key, final String... strs) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.rpush(key, strs);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public <T> T rpop(final String key, final Class<T> clazz) {
        return execute(new JedisExecutor<T>() {
            @Override
            public T execute(Jedis jedis) {
                return (T) parseObject(jedis.rpop(key), clazz);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public <T> T rpop(final String key, final TypeReference<T> type) {
        return execute(new JedisExecutor<T>() {
            @Override
            public T execute(Jedis jedis) {
                return (T) parseObject(jedis.rpop(key), type);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> lrange(final String key, final long start, final long end, Class<T> clazz) {
        List<String> list = execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.lrange(key, start, end);
            }
        });
        if (list == null) {
            return null;
        }
        if (list.isEmpty()) {
            return Collections.emptyList();
        }

        List<T> vals = new ArrayList<T>();
        for (String item : list) {
            vals.add((T) parseObject(item, clazz));
        }
        return vals;
    }

    public List<String> lrange(final String key, final long start, final long end) {
        List<String> list = execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.lrange(key, start, end);
            }
        });

        if (list == null) {
            return null;
        }
        if (list.isEmpty()) {
            return Collections.emptyList();
        }

        return list;
    }

    public Long llen(final String key) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.llen(key);
            }
        });
    }

    public Long ltrim(final String key, final long start, final long end) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                jedis.ltrim(key, start, end);
                return 1L;
            }
        });
    }

    public Long lrem(final String key, final long count, final String value) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.lrem(key, count, value);
            }
        });
    }

    public Long hlen(final String key) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hlen(key);
            }
        });
    }

    @SuppressWarnings("unchecked")
    private static <T> T parseObject(String val, Object clazz) {
        if (clazz instanceof TypeReference) {
            return JsonConverter.parse(val, (TypeReference<T>) clazz);
        }

        if (clazz == String.class) {
            return (T) val;
        }

        return JsonConverter.parse(val, (Class<T>) clazz);
    }

    private static String toJsonString(Object o) {
        String value = "";
        if (o instanceof String) {
            value = o.toString();
        } else {
            value = JsonConverter.format(o);
        }
        return value;
    }

    public Set<String> hkeys(final String key) {
        return execute(new JedisExecutor<Set<String>>() {
            @Override
            public Set<String> execute(Jedis jedis) {
                return jedis.hkeys(key);
            }
        });

    }

    public <T> T hget(String key, String field, Class<T> clazz) {
        String val = hashGet(key, field);
        return parseObject(val, clazz);
    }

    public <T> T hget(String key, String field, TypeReference<T> type) {
        String val = hashGet(key, field);
        return parseObject(val, type);
    }

    public String hget(String key, String field) {
        return hashGet(key, field);
    }



    public void hset(String key, String field, Object value) {
        if (value instanceof String) {
            hashSet(key, field, (String) value);
        } else {
            hashSet(key, field, JsonConverter.format(value));
        }
    }

    private Long hashSet(final String key, final String field, final String value) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hset(key, field, value);
            }
        });
    }

    private String hashGet(final String key, final String field) {
        return execute(new JedisExecutor<String>() {

            @Override
            public String execute(Jedis jedis) {
                String result = jedis.hget(key, field);
                return result;
            }
        });
    }

    public Map<String, String> hgetall(final String key) {
        return execute(new JedisExecutor<Map<String, String>>() {
            @Override
            public Map<String, String> execute(Jedis jedis) {
                return jedis.hgetAll(key);
            }
        });
    }

    @SuppressWarnings("unchecked")
    public <T> List<T> hvals(final String key, Class<T> clazz) {
        List<String> list = execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.hvals(key);
            }
        });
        if (list == null) {
            return null;
        }
        if (list.isEmpty()) {
            return Collections.emptyList();
        }

        List<T> vals = new ArrayList<T>();
        for (String item : list) {
            vals.add((T) parseObject(item, clazz));
        }
        return vals;
    }

    public List<String> hvals(final String key) {
        List<String> list = execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.hvals(key);
            }
        });
        return list;

    }



    public String hmset(final String key, Map<String, Object> map) {
        if (map == null || map.size() == 0) {
            return null;
        }

        final Map<String, String> jsonMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            jsonMap.put(entry.getKey(), toJsonString(entry.getValue()));
        }

        return execute(new JedisExecutor<String>() {

            @Override
            public String execute(Jedis jedis) {
                return jedis.hmset(key, jsonMap);
            }
        });
    }

    public List<String> hmget(final String key, final String... fields) {
        return execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.hmget(key, fields);
            }
        });
    }

    public Set<String> keys(final String pattern) {
        return execute(new JedisExecutor<Set<String>>() {
            @Override
            public Set<String> execute(Jedis jedis) {
                return jedis.keys(pattern);
            }
        });
    }

    public boolean exists(final String key) {
        return execute(new JedisExecutor<Boolean>() {
            @Override
            public Boolean execute(Jedis jedis) {
                return jedis.exists(key);
            }
        });
    }

    public boolean hexists(final String key, final String field) {
        return execute(new JedisExecutor<Boolean>() {
            @Override
            public Boolean execute(Jedis jedis) {
                return jedis.hexists(key, field);
            }
        });
    }

    public Long hincrBy(final String key, final String field, final long value) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hincrBy(key, field, value);
            }
        });
    }

    public Long hdel(final String key, final String field) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.hdel(key, field);
            }
        });
    }

    public Boolean sismember(final String key, final String member) {
        return execute(new JedisExecutor<Boolean>() {
            @Override
            public Boolean execute(Jedis jedis) {
                return jedis.sismember(key, member);
            }
        });
    }

    public List<String> srandmember(final String key, final int count) {
        return execute(new JedisExecutor<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                return jedis.srandmember(key, count);
            }
        });
    }

    public long sadd(final String key, final String member) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.sadd(key, member);
            }
        });
    }

    /**
     * 删除set集合元素  add by huangwd 2018.01.22
     * @param key
     * @param member
     * @return
     */
    public long srem(final String key, final String... member) {
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.srem(key, member);
            }
        });
    }

    /**
     * 设置key超时时间
     * 
     * @param key 缓存key
     * @param timeout 超时时间，单位秒
     */
    public Long expire(final String key, final int timeout) {
        /* expire(key, timeout, TimeUnit.SECONDS); */
        return execute(new JedisExecutor<Long>() {
            @Override
            public Long execute(Jedis jedis) {
                return jedis.expire(key, timeout);
            }
        });
    }

    public <T> T execute(JedisExecutor<T> jedisExecutor) throws JedisException {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedisExecutor.execute(jedis);
        } catch (JedisConnectionException e) {
            LogManager.getLogger(RedisAdapter.class).error("Redis connection failed.", e);
            throw e;
        } finally {
            closeResource(jedis);
        }
    }

    /**
     * @param jedis
     */
    private void closeResource(Jedis jedis) {
        if (jedis != null) {
            try {
                jedis.close();
            } catch (Exception e) {
                LogManager.getLogger(RedisAdapter.class).error(
                        "Error happen when return jedis to pool, try to close it directly.", e);
            }
        }
    }
}


/**
 * 缓存json解析器
 * 
 * @author zhaoyc
 * @version 创建时间：2017年9月4日 下午3:52:34
 */
class JsonConverter {
    public static String format(Object obj) {
        return JSON.toJSONString(obj);
    }

    public static <T> T parse(String str, Class<T> clazz) {
        return JSON.parseObject(str, clazz);
    }

    public static <T> List<T> parseArray(String str, Class<T> clazz) {
        return JSON.parseArray(str, clazz);
    }

    public static <T> T parse(String str, TypeReference<T> type) {
        return JSON.parseObject(str, type);
    }

}


/**
 * 缓存执行器
 * 
 * @author zhaoyc
 * @version 创建时间：2017年9月4日 下午3:52:47
 * @param <T>
 */
interface JedisExecutor<T> {

    /**
     * 执行动作
     * 
     * @param jedis
     * @return
     */
    public T execute(Jedis jedis);

}
